package Day_0223.Spring;

/**
 * @author zxc
 * @date 2023/02/23 22:04
 **/
public class Cyclic_Dependency_L3_Cache {
    public static void main(String[] args) {
        /**
         * 谈一谈对于循环依赖和三级缓存的理解
         * ===》
         * 一，循环依赖机制
         * 1.Spring中的循环依赖，其实就是一个死循环的过程，
         * 在初始化 A 的时候发现依赖了 B，这时就会去初始化 B，然后又发现 B 依赖 C，跑去初始化 C，初始化C 的时候发现依赖了 A，
         * 则又会去初始化 A，依次循环永不退出，除非有终结条件。
         *
         * 2.一般来说，Spring 循环依赖的情况有两种：
         * 1）构造器的循环依赖。
         * ===》
         * 对于构造器的循环依赖，Spring 是无法解决的，只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖，
         * 2）field 属性的循环依赖。
         * 基于 field 属性的循环依赖，也要分两种细分的情况来对待：
         * （1）scope 为 singleton 的循环依赖。
         * （2）scope 为 prototype 的循环依赖。
         * ===》
         *  即，当scope为 prototype（多例）时，直接抛出BeanCurrentlyInCreationException 异常。
         *
         * 二，三级缓存机制
         * 1.spring内部有三级缓存：
         * （1）三级缓存是singletonFactories ，但是是不完整的Bean的工厂Factory,
         *  是当一个Bean在new之后（没有属性填充、初始化），就put进去。
         * （2）二级缓存是对三级缓存的过渡性处理，只要通过 getObject() 方法从三级缓存的BeanFactory中取出Bean一次，
         *  原材料就变成变成品，就put到二级缓存。
         * （2）一级缓存里面是完整的Bean,是当一个Bean完全创建后(完成属性填充、彻底的完成初始化）才put进去。
         *
         * 2.三级缓存解决循环依赖的详细解释
         * 当发现某个循环依赖的时候，
         * （1）首先，去一级缓存SingletonObjects中，加载指定的单例Bean;
         *  ===》即，当前Bean正在创建的过程中，还未加载完成;
         * （2）若是一级缓存中不存在指定的单例Bean的话，则选择去二级缓存EarlySingletonObjects获取指定的单例Bean;
         *  ===》即，若是二级缓存中不存在的话，则允许提前创建;
         * （3）从三级缓存SingletonFactories中，获取对应的BeanFactory;
         *  ===》
         *  若是本类未能在三级缓存中寻找到对应的Bean，则会选择将自身类对应的BeanFactory && 将其加载到三级缓存中;
         * （4）若是能够从三级缓存中拿到对应的BeanFactory,通过getObject方法，来生产一个原始Bean（未进行属性配置 && 未进行初始化）;
         *  ===》
         *  由于使用BeanFactory去创建了对应的Bean（未属性赋值，初始化），
         *  则将对应的BeanFactory从三级缓存中删除，同时将 根据BeanFactory新创建出来的Bean，加到二级缓存中;
         *
         * 3.三级缓存（SingletonFactories）详解
         * 1）put三级缓存 singletonFactories 的地方，
         * 其核心逻辑是，当满足以下3个条件时，把bean加入三级缓存中：
         * （1）单例;
         * （2）允许循环依赖;
         * （3）当前单例Bean正在创建;
         *
         * 2）设置到三级缓存之后，对于其他依赖它的对象而言，（已经有内存地址了，可以根据对象引定位到堆中对象）。
         *
         * 三，Spring如何解决循环依赖
         * 1.原型(Prototype)的场景是不支持循环依赖的
         * ===》
         * （1）前提 ：原型(Prototype)的场景是不支持循环依赖的。
         * （2）单例的场景，才能存在循环依赖
         *
         * 2.原型(Prototype)的场景通常会走到 AbstractBeanFactory 类中下面的判断，抛出异常。
         * if (isPrototypeCurrentlyInCreation(beanName)) {
         * throw new BeanCurrentlyInCreationException(beanName);
         * }
         *
         * 3.单例（singleton）和原型（prototype）之间的区别
         * （1）所谓单例，就是Spring的IOC机制只创建该类的一个实例，
         *  每次请求，都会用这同一个实例进行处理，因此若存在全局变量，本次请求的值肯定会影响下一次请求时该变量的值。
         *  ===》
         *  若不想影响下次请求，就需要用到原型模式，即@Scope(“prototype”)
         * （2）原型模式，
         *  指的是每次调用时，会重新创建该类的一个实例，比较类似于我们自己自己new的对象实例。
         *
         * 四，循环依赖的场景
         * 1.单例的Setter注入（可以解决）
         * 解决单例的Setter注入所采用的方法：
         * ===》三级缓存：
         * （1）一级缓存 singletonObjects ，用于保存实例化、注入、初始化完成的bean实例 ：
         * （2）二级缓存 earlySingletonObjects ，用于保存实例化完成的bean实例
         * （3）三级缓存 singletonFactories ，用于保存bean创建工厂，以便于后面扩展有机会创建代理对象。
         *
         * 2.多例的Setter注入
         * 1.谈一谈多例的Setter注入为啥不会报 循环依赖的错误
         * ===》
         * （1）其实在 AbstractApplicationContext 类的 refresh 方法中告诉了我们答案，
         * （2）它会调用 finishBeanFactoryInitialization 方法，该方法的作用是，为了spring容器启动的时候，提前初始化一些bean。
         *  该方法的内部又调用了 preInstantiateSingletons 方法
         *
         * （3）重点 ：非抽象、单例 并且非懒加载的类才能被提前初始bean。
         *  ===》
         *  多例，即 SCOPE_PROTOTYPE 类型的类，非单例，不会被提前初始化bean，所以程序能够正常启动。
         *
         * 3.构造器注入
         * ===》
         * 构造器注入没能添加到三级缓存，也没有使用缓存，所以也无法解决循环依赖问题。
         *
         * 4.单例的代理对象Setter注入
         * ===》
         * 1）这种注入方式其实也比较常用，比如平时使用： @Async 注解的场景，会通过 AOP 自动生成代理对象。
         * 2）与单例的Setter注入 的差异 ：
         * bean初始化完成之后，后面还有一步去检查：第二级缓存 和 原始对象 是否相等。
         *
         * 5.DependsOn循环依赖
         * ===》
         * （1）它会检查dependsOn的实例有没有循环依赖，如果有循环依赖则抛异常。
         * （2）若是加上DependsOn注解 && 存在循环依赖，但是同时开启了三级缓存机制的话，仍然会去报循环依赖的错误;
         *
         * ===》
         * 总结 ：
         * 1.生成代理对象产生的循环依赖 的解决方案：
         * （1）使用 @Lazy 注解，延迟加载
         * （2）使用 @DependsOn 注解，指定加载先后关系
         * （3）修改文件名称，改变循环依赖类的加载顺序
         *
         * 2.使用@DependsOn产生的循环依赖 的解决方案：
         * 这类循环依赖问题要找到 @DependsOn 注解循环依赖的地方，迫使它不循环依赖就可以解决问题。
         *
         * 3.多例循环依赖 的解决方案：
         * ===》
         * 这类循环依赖问题可以通过把bean改成单例的解决。
         *
         * 4.构造器循环依赖的解决方案：
         * ===》
         * 可以通过使用 @Lazy 注解解决
         *
         * 5.单例Setter注入依赖的解决方案 ：
         * ===》
         * 通过三级缓存机制，来解决单例Setter依赖注入;
         *
         * 六，Spring是怎么解决循环依赖的？
         * 1.首先，Spring 解决循环依赖有两个前提条件：
         * （1）不全是构造器方式的循环依赖;
         * （2）必须是单例;
         *
         * 2.基于上面的问题，知道Bean的生命周期，本质上解决循环依赖的问题就是三级缓存，通过三级缓存提前拿到未初始化的对象。
         * （1）第三级缓存（SingletonFactories）：用来保存一个对象工厂BeanFactory，提供一个匿名内部类，用于创建二级缓存中的对象;
         * （2）第二级缓存（EarlySingletonObjects）：用来保存实例化完成，但是未初始化完成的对象Bean;
         * （3）第一级缓存（SingletonObjects）：用来保存实例化、初始化都完成的对象Bean;
         *
         */
    }
}
